package com.dj.mingkong.common.util;


import com.dj.mingkong.common.infrastructure.base.dto.page.PageVo;
import com.dj.mingkong.common.exception.ValidateException;
import com.google.common.collect.Lists;
import ma.glasnost.orika.DefaultFieldMapper;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory.Builder;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author zenglonghui
 * @date: 2022/5/20 16:35
 */
public class SmartBeanCopy {
    private static final Logger logger = LoggerFactory.getLogger(SmartBeanCopy.class);
    private static final String RECORDS = "records";
    private static final MapperFactory MAPPER_FACTORY = (new Builder()).build();
    private static final MapperFacade MAPPER_FACADE = null;
    private static Map<String, MapperFacade> CACHE_MAPPER_FACADE_MAP;

    private SmartBeanCopy() {
    }

    /**
     * @deprecated
     */
    @Deprecated
    public static <T, K> List<K> copyWithoutEnum(List<T> ts, Class<K> kClass) {
        return (List) (CollectionUtils.isEmpty(ts) ? new ArrayList() : MAPPER_FACADE.mapAsList(ts, kClass));
    }

    /**
     * @deprecated
     */
    @Deprecated
    public static <T, K> K copyWithoutEnum(T t, Class<K> k) {
        return t == null ? null : MAPPER_FACADE.map(t, k);
    }

    public static <T, K> List<K> copy(Collection<T> ts, Class<K> kClass) {
        if (CollectionUtils.isEmpty(ts)) {
            return Collections.emptyList();
        }
        return ts.stream().map(s -> copy(s, kClass)).toList();
    }

    public <E, T> E copy(Class<E> toClass, T data, Map<String, String> configMap) {
        MapperFacade mapperFacade = this.getMapperFacade(toClass, data.getClass(), configMap);
        return mapperFacade.map(data, toClass);
    }

    private <E, T> MapperFacade getMapperFacade(Class<E> toClass, Class<T> dataClass, Map<String, String> configMap) {
        String mapKey = dataClass.getCanonicalName() + "_" + toClass.getCanonicalName();
        MapperFacade mapperFacade = (MapperFacade) CACHE_MAPPER_FACADE_MAP.get(mapKey);
        if (Objects.isNull(mapperFacade)) {
            MapperFactory factory = (new Builder()).build();
            ClassMapBuilder<?, ?> classMapBuilder = factory.classMap(dataClass, toClass);
            configMap.forEach(classMapBuilder::field);
            classMapBuilder.byDefault(new DefaultFieldMapper[0]).register();
            mapperFacade = factory.getMapperFacade();
            CACHE_MAPPER_FACADE_MAP.put(mapKey, mapperFacade);
        }

        return mapperFacade;
    }

    public static <T, K> K copy(T t, Class<K> kClass) {
        try {
            var k1 = kClass.getConstructor().newInstance();
            if (t == null) {
                return k1;
            }
            BeanUtils.copyProperties(t, k1);
            return k1;
        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | InstantiationException var4) {
            logger.error("Class {} prevent ListBeanCopy getting it's instance. Please check!", kClass.getName());
            throw new ValidateException("Bean copy error");
        }

    }

    /**
     * @deprecated
     */
    @Deprecated
    public static <T, K> List<K> copyProperties(Collection<T> ts, Class<K> kClass) {
        if (CollectionUtils.isEmpty(ts)) {
            return Collections.emptyList();
        } else {
            List<K> k1 = new ArrayList(ts.size());
            ts.forEach((e) -> {
                k1.add(copyProperties(e, kClass));
            });
            return k1;
        }
    }

    /**
     * @deprecated
     */
    @Deprecated
    public static <T, K> K copyProperties(T t, Class<K> kClass) {
        if (t == null) {
            return null;
        } else {
            Object k1 = null;

            try {
                k1 = kClass.getConstructor().newInstance();
            } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | InstantiationException var4) {
                logger.error("Class {} prevent ListBeanCopy getting it's instance. Please check!", kClass.getName());
                return null;
            }

            BeanUtils.copyProperties(t, k1);
            return (K) k1;
        }
    }

    public static <T, K> PageVo<K> copyPageVo(Object pageO, Class<K> kClass) {
        if (pageO != null && kClass != null) {
            PageVo<K> result = new PageVo();
            BeanUtils.copyProperties(pageO, result, new String[]{"records"});
            Object fieldValue = ReflectUtils.getFieldValue(pageO, "records");
            if (fieldValue instanceof Collection && CollectionUtils.isNotEmpty((Collection) fieldValue)) {
                result.setRecords(copy((Collection) fieldValue, kClass));
            }

            return result;
        } else {
            return new PageVo();
        }
    }

    public static <T, K> PageVo<K> copyPageVo(Object pageO, List<K> list) {
        if (pageO != null && !CollectionUtils.isEmpty(list)) {
            PageVo<K> result = new PageVo();
            BeanUtils.copyProperties(pageO, result, new String[]{"records"});
            result.setRecords(list);
            return result;
        } else {
            return new PageVo();
        }
    }

    public static <T, K, U> List<T> one2Many(List<T> parents, List<K> children, Function<? super T, ? extends U> parentId, Function<? super K, ? extends U> childId, BiConsumer<? super T, ? super List<K>> action) {
        if (!CollectionUtils.isEmpty(parents) && !CollectionUtils.isEmpty(children)) {
            Map<U, T> pMap = (Map) parents.stream().collect(Collectors.toMap(parentId, (p) -> {
                return p;
            }));
            Map<U, List<K>> cMap = (Map) children.stream().collect(Collectors.toMap(childId, (xva$0) -> {
                return Lists.newArrayList(new Object[]{xva$0});
            }, (oldList, newList) -> {
                oldList.addAll(newList);
                return oldList;
            }));
            pMap.forEach((k, v) -> {
                action.accept(v, cMap.get(k));
            });
            return parents;
        } else {
            return new ArrayList();
        }
    }

    public static <T, K, U> List<T> one2One(List<T> parents, List<K> children, Function<? super T, ? extends U> parentId, Function<? super K, ? extends U> childId, BiConsumer<? super T, ? super K> action) {
        if (!CollectionUtils.isEmpty(parents) && !CollectionUtils.isEmpty(children)) {
            Map<U, T> pMap = (Map) parents.stream().collect(Collectors.toMap(parentId, (p) -> {
                return p;
            }));
            Map<U, K> cMap = (Map) children.stream().collect(Collectors.toMap(childId, (c) -> {
                return c;
            }));
            pMap.forEach((k, v) -> {
                action.accept(v, cMap.get(k));
            });
            return parents;
        } else {
            return new ArrayList();
        }
    }

    public static <K, U> Map<U, K> toMap(List<K> list, Function<? super K, ? extends U> function) {
        return (Map) (!CollectionUtils.isEmpty(list) && function != null ? (Map) list.stream().collect(Collectors.toMap(function, (c) -> {
            return c;
        })) : new HashMap(0));
    }

/*    public static void appendMapOne2OneIsRemove(List<Map<String, Object>> targets, List<Map<String, Object>> sources, String targetKey, String sourceKey, boolean isRemoveNotMapping) {
        appendMapOne2One(targets, sources, targetKey, sourceKey, (BiConsumer)null, isRemoveNotMapping);
    }*/

/*    public static void appendMapOne2One(List<Map<String, Object>> targets, List<Map<String, Object>> sources, String targetKey, String sourceKey, BiConsumer<Map, List<Map<String, Object>>> specialLogic, boolean isRemoveNotMapping) {
        if (!CollectionUtils.isEmpty(targets) && !CollectionUtils.isEmpty(sources)) {
            Iterator iterator = targets.iterator();

            while(true) {
                while(true) {
                    Map target;
                    do {
                        if (!iterator.hasNext()) {
                            return;
                        }

                        target = (Map)iterator.next();
                    } while(!target.containsKey(targetKey));

                    List<Map<String, Object>> collect = (List)((Stream)sources.stream().parallel()).filter((source) -> {
                        return source.get(sourceKey).equals(target.get(targetKey));
                    }).collect(Collectors.toList());
                    if (specialLogic != null) {
                        specialLogic.accept(target, collect);
                    }

                    if (!CollectionUtils.isEmpty(collect) && collect.size() <= 1) {
                        Map<String, Object> sourceMapped = (Map)collect.get(0);
                        target.putAll(sourceMapped);
                    } else if (isRemoveNotMapping) {
                        iterator.remove();
                    }
                }
            }
        }
    }*/

    public static <T> String collectIds(List<T> ts, Function<T, Long> function) {
        return CollectionUtils.isEmpty(ts) ? null : (String) ((List) ((Stream) ts.stream().parallel()).filter((t) -> {
            return Objects.nonNull(function.apply((T) t));
        }).map(function).collect(Collectors.toList())).stream().map(Object::toString).collect(Collectors.joining(","));
    }

    public static <T> String collectStrs(List<T> ts, Function<T, String> function) {
        return CollectionUtils.isEmpty(ts) ? null : (String) ((List) ((Stream) ts.stream().parallel()).filter((t) -> {
            return Objects.nonNull(function.apply((T) t));
        }).map(function).collect(Collectors.toList())).stream().map(Object::toString).collect(Collectors.joining(","));
    }

 /*   public static <T> List<Long> collectStringToIdList(List<T> ts, Function<T, String> function) {
        return CollectionUtils.isEmpty(ts) ? null : (List)((List)((Stream)ts.stream().parallel()).filter((t) -> {
            return Objects.nonNull(function.apply((T) t));
        }).map(function).collect(Collectors.toList())).stream().filter(StringUtils::isNotEmpty).map(Long::parseLong).collect(Collectors.toList());
    }*/

    public static <T> List<String> collectStringToStrList(List<T> ts, Function<T, String> function) {
        return CollectionUtils.isEmpty(ts) ? null : (List) ((Stream) ts.stream().parallel()).filter((t) -> {
            return Objects.nonNull(function.apply((T) t));
        }).map(function).collect(Collectors.toList());
    }

    public static <T> List<Long> collectIdToIdList(List<T> ts, Function<T, Long> function) {
        return CollectionUtils.isEmpty(ts) ? null : (List) ((Stream) ts.stream().parallel()).filter((t) -> {
            return Objects.nonNull(function.apply((T) t));
        }).map(function).collect(Collectors.toList());
    }

    public static <T> List<String> collectIdToStrList(List<T> ts, Function<T, Long> function) {
        return CollectionUtils.isEmpty(ts) ? null : (List) ((List) ((Stream) ts.stream().parallel()).filter((t) -> {
            return Objects.nonNull(function.apply((T) t));
        }).map(function).collect(Collectors.toList())).stream().map(Object::toString).collect(Collectors.toList());
    }

    public static <K, T> Map<K, T> collectObjsByKey(List<T> ts, Function<T, K> function) {
        return CollectionUtils.isEmpty(ts) ? null : (Map) ts.parallelStream().collect(Collectors.toMap(function, (a) -> {
            return a;
        }));
    }

    public Class<?> getGenericClass() {
        Type t = this.getClass().getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            Type[] types = ((ParameterizedType) t).getActualTypeArguments();
            return (Class) types[0];
        } else {
            return null;
        }
    }

    static {
        //MAPPER_FACADE = MAPPER_FACTORY.getMapperFacade();
        CACHE_MAPPER_FACADE_MAP = new ConcurrentHashMap();
    }
}
